---@diagnostic disable: lowercase-global, undefined-global
local version = "1.0.0"

-- 引入触动精灵基础库
local ts = require("TSLib")  -- 触动精灵函数库
local sz = require("sz")
local json = sz.json

-- 全局变量
local width, height = 0, 0  -- 初始化屏幕宽高变量
local apiKey = "YOUR_API_KEY"  -- 替换为你的OpenAI API密钥

-- 副本关键词库 - 用于本地简单过滤
local dungeonKeywords = {
    ["中文"] = {"副本", "挑战", "闯关", "战斗", "boss", "通关", "难度", "普通", "困难", "地下城"},
    ["英文"] = {"dungeon", "raid", "challenge", "battle", "boss", "fight", "difficulty", "normal", "hard", "instance"}
}

-- 数据库结构
local npcDatabase = {
    lastUpdate = os.time(),
    npcs = {}
}

-- 记录Boss击杀时间的表
local bossKillTimes = {}

-- 地图区域管理
local MapRegion = {
    -- 已扫描区域记录
    scannedAreas = {},
    -- 待扫描区域队列
    pendingAreas = {},
    -- 区域大小设置
    regionSize = {
        width = 200,  -- 每个区域宽度
        height = 200  -- 每个区域高度
    },
    -- 扫描进度
    progress = {
        totalRegions = 0,
        scannedRegions = 0
    }
}

-- 打印日志函数
local function log(text)
    local timeStr = os.date("%Y-%m-%d %H:%M:%S")
    local logText = "[" .. timeStr .. "] " .. text
    nLog(logText)        -- 写入日志文件
end

-- 截取当前屏幕
function captureGameScreen()
    local imgPath = userPath().."/screen_capture.png"
    snapshot(0, 0, width, height, imgPath)
    log("已截取游戏画面: " .. imgPath)
    return imgPath
end

-- 图像转Base64
function imageToBase64(imgPath)
    local file = io.open(imgPath, "rb")
    if not file then
        log("错误: 无法打开图像文件: " .. imgPath)
        return nil
    end
    local content = file:read("*all")
    file:close()
    return ts.base64encode(content) -- 触动精灵的base64编码函数
end

-- 构建发送给ChatGPT的识别NPC请求
function buildNPCDetectionRequest(base64Image)
    local request = {
        model = "gpt-4-vision", -- 使用支持图像的模型
        messages = {
            {
                role = "system",
                content = "你是一个游戏画面分析助手。请分析截图中的游戏画面，识别所有NPC角色的位置(x,y坐标)并描述他们的外观特征。"
            },
            {
                role = "user",
                content = {
                    {
                        type = "text",
                        text = "这是一个游戏截图，请帮我识别所有可能的NPC位置(给出坐标)以及它们的特征。请以JSON格式返回：[{\"name\":\"NPC名称\",\"x\":坐标X,\"y\":坐标Y}]"
                    },
                    {
                        type = "image_url",
                        image_url = {
                            url = "data:image/png;base64," .. base64Image
                        }
                    }
                }
            }
        }
    }
    return json.encode(request)
end

-- 构建发送给ChatGPT的分析对话请求
function buildDialogAnalysisRequest(base64Image)
    local request = {
        model = "gpt-4-vision",
        messages = {
            {
                role = "system",
                content = "你是游戏对话分析专家。请分析截图中的NPC对话，判断是否提供副本功能，并识别所有可用副本的名称以及它们在界面中的位置(x,y坐标)。"
            },
            {
                role = "user",
                content = {
                    {
                        type = "text",
                        text = "这是一个游戏NPC对话截图，请分析是否有副本入口，如果有，指出副本名称和屏幕上的位置。请以JSON格式返回：{\"hasDungeon\":true/false,\"dungeons\":[{\"name\":\"副本名称\",\"x\":坐标X,\"y\":坐标Y}]}"
                    },
                    {
                        type = "image_url",
                        image_url = {
                            url = "data:image/png;base64," .. base64Image
                        }
                    }
                }
            }
        }
    }
    return json.encode(request)
end

-- 发送请求到ChatGPT API
function sendToChatGPT(requestData)
    log("发送请求到ChatGPT API...")
    local response = httpPost("https://api.openai.com/v1/chat/completions", 
        requestData,
        30, -- 超时时间
        {["Content-Type"] = "application/json", ["Authorization"] = "Bearer " .. apiKey}
    )
    if not response then
        log("错误: 请求ChatGPT API失败")
        return nil
    end
    return json.decode(response)
end

-- 从ChatGPT响应中提取JSON
function extractJSONFromResponse(response)
    if not response or not response.choices or not response.choices[1] or not response.choices[1].message then
        log("错误: 无效的ChatGPT响应")
        return nil
    end
    
    local content = response.choices[1].message.content
    log("ChatGPT响应内容: " .. content)
    
    -- 尝试从文本中提取JSON
    local jsonStart = content:find("%[") or content:find("{")
    local jsonEnd = content:find("%].-$") or content:find("}.-$")
    
    if jsonStart and jsonEnd then
        local jsonStr = content:sub(jsonStart, jsonEnd)
        log("提取的JSON: " .. jsonStr)
        local success, result = pcall(json.decode, jsonStr)
        if success then
            return result
        else
            log("错误: JSON解析失败: " .. result)
        end
    else
        log("错误: 无法从响应中提取JSON")
    end
    
    return nil
end

-- 解析ChatGPT返回的NPC位置信息
function parseNPCLocations(response)
    local jsonData = extractJSONFromResponse(response)
    if not jsonData then
        return {}
    end
    
    -- 如果是数组格式
    if type(jsonData) == "table" and #jsonData > 0 then
        log("成功解析到" .. #jsonData .. "个NPC位置")
        return jsonData
    end
    
    log("警告: 无法从响应中解析NPC位置")
    return {}
end

-- 解析ChatGPT返回的副本信息
function parseDungeonInfo(response)
    local jsonData = extractJSONFromResponse(response)
    if not jsonData then
        return { hasDungeon = false, dungeons = {} }
    end
    
    -- 检查是否包含需要的字段
    if type(jsonData) == "table" and jsonData.hasDungeon ~= nil then
        log("成功解析副本信息: " .. (jsonData.hasDungeon and "有副本" or "无副本"))
        if jsonData.dungeons and #jsonData.dungeons > 0 then
            log("发现" .. #jsonData.dungeons .. "个副本")
        end
        return jsonData
    end
    
    log("警告: 无法从响应中解析副本信息")
    return { hasDungeon = false, dungeons = {} }
end

-- 移动到指定坐标
function moveToPosition(x, y)
    log("移动到坐标: " .. x .. "," .. y)
    tap(x, y)
    mSleep(1000) -- 等待移动
end

-- 与NPC交互并截取对话界面
function interactWithNPC(npc)
    log("与NPC交互: " .. (npc.name or "未知NPC") .. " 位置: " .. npc.x .. "," .. npc.y)
    moveToPosition(npc.x, npc.y)
    tap(npc.x, npc.y)
    mSleep(1500) -- 等待对话框出现
    
    local dialogPath = userPath().."/dialog_" .. os.time() .. ".png"
    snapshot(0, 0, width, height, dialogPath)
    log("已截取对话界面: " .. dialogPath)
    return dialogPath
end

-- 保存数据库到本地文件
function saveNPCDatabase()
    local filePath = userPath().."/npc_database.json"
    local file = io.open(filePath, "w")
    if file then
        file:write(json.encode(npcDatabase))
        file:close()
        log("NPC数据库已保存到: " .. filePath)
    else
        log("错误: 无法保存NPC数据库到: " .. filePath)
    end
end

-- 加载本地数据库
function loadNPCDatabase()
    local filePath = userPath().."/npc_database.json"
    local file = io.open(filePath, "r")
    if file then
        local content = file:read("*all")
        file:close()
        
        local success, result = pcall(json.decode, content)
        if success then
            npcDatabase = result
            log("已加载NPC数据库，包含" .. #npcDatabase.npcs .. "个NPC记录")
        else
            log("错误: 解析NPC数据库失败: " .. result)
        end
    else
        log("未找到NPC数据库，将创建新的数据库")
    end
end

-- 添加或更新NPC信息
function updateNPCInfo(npc, dungeonInfo)
    -- 检查是否已存在该NPC
    local found = false
    for i, existingNPC in ipairs(npcDatabase.npcs) do
        -- 通过坐标接近度判断是否为同一NPC
        if math.abs(existingNPC.x - npc.x) < 20 and math.abs(existingNPC.y - npc.y) < 20 then
            -- 更新NPC信息
            npcDatabase.npcs[i].name = npc.name or existingNPC.name
            npcDatabase.npcs[i].x = npc.x
            npcDatabase.npcs[i].y = npc.y
            npcDatabase.npcs[i].hasDungeon = dungeonInfo.hasDungeon
            npcDatabase.npcs[i].dungeons = dungeonInfo.dungeons
            npcDatabase.npcs[i].lastUpdate = os.time()
            found = true
            log("更新NPC信息: " .. (npc.name or "未知NPC"))
            break
        end
    end
    
    -- 如果是新NPC，添加到数据库
    if not found then
        table.insert(npcDatabase.npcs, {
            name = npc.name or "未知NPC",
            x = npc.x,
            y = npc.y,
            hasDungeon = dungeonInfo.hasDungeon,
            dungeons = dungeonInfo.dungeons,
            lastUpdate = os.time()
        })
        log("添加新NPC: " .. (npc.name or "未知NPC"))
    end
    
    -- 保存更新后的数据库
    npcDatabase.lastUpdate = os.time()
    saveNPCDatabase()
end

-- 使用关键词初步判断是否有副本内容
function preScanForDungeonKeywords(text)
    text = text:lower() -- 转小写以忽略大小写
    
    -- 检查中文关键词
    for _, keyword in ipairs(dungeonKeywords["中文"]) do
        if text:find(keyword) then
            return true
        end
    end
    
    -- 检查英文关键词
    for _, keyword in ipairs(dungeonKeywords["英文"]) do
        if text:find(keyword) then
            return true
        end
    end
    
    return false
end

-- 保存关键词库
function saveKeywordsLibrary()
    local filePath = userPath().."/dungeon_keywords.json"
    local file = io.open(filePath, "w")
    if file then
        file:write(json.encode(dungeonKeywords))
        file:close()
        log("副本关键词库已保存到: " .. filePath)
    else
        log("错误: 无法保存副本关键词库到: " .. filePath)
    end
end

-- 加载关键词库
function loadKeywordsLibrary()
    local filePath = userPath().."/dungeon_keywords.json"
    local file = io.open(filePath, "r")
    if file then
        local content = file:read("*all")
        file:close()
        
        local success, result = pcall(json.decode, content)
        if success then
            dungeonKeywords = result
            log("已加载副本关键词库")
        else
            log("错误: 解析副本关键词库失败: " .. result)
        end
    else
        log("未找到副本关键词库，将使用默认关键词")
    end
end

-- 表中是否包含某元素
function table.contains(t, element)
    for _, value in pairs(t) do
        if value == element then
            return true
        end
    end
    return false
end

-- 从ChatGPT响应中提取关键词
function extractKeywords(text)
    local keywords = {}
    -- 分割文本，提取关键词
    for keyword in text:gmatch("[^\n:,，。；;%s]+") do
        if #keyword > 1 then -- 排除单个字符
            table.insert(keywords, keyword)
        end
    end
    return keywords
end

-- 根据交互结果自动更新关键词库
function updateKeywordsLibrary(text, isDungeon)
    -- 如果确认是副本，但没有匹配到任何关键词，可能需要更新关键词库
    if isDungeon and not preScanForDungeonKeywords(text) then
        -- 使用ChatGPT分析可能的新关键词
        local request = {
            model = "gpt-4",
            messages = {
                {
                    role = "system",
                    content = "你是游戏文本分析专家。请从以下文本中提取可能与'副本'或'挑战'相关的关键词，这些词可能标志着游戏中的副本入口。"
                },
                {
                    role = "user",
                    content = "从以下文本中提取可能的副本关键词（单个词语）：\n" .. text
                }
            }
        }
        
        local response = sendToChatGPT(json.encode(request))
        if response then
            local keywords = extractKeywords(response.choices[1].message.content)
            
            -- 更新关键词库
            for _, keyword in ipairs(keywords) do
                if string.match(keyword, "^[a-zA-Z]+$") then
                    -- 英文关键词
                    if not table.contains(dungeonKeywords["英文"], keyword) then
                        table.insert(dungeonKeywords["英文"], keyword)
                        log("添加新的英文副本关键词: " .. keyword)
                    end
                else
                    -- 中文关键词
                    if not table.contains(dungeonKeywords["中文"], keyword) then
                        table.insert(dungeonKeywords["中文"], keyword)
                        log("添加新的中文副本关键词: " .. keyword)
                    end
                end
            end
            
            -- 保存更新后的关键词库
            saveKeywordsLibrary()
        end
    end
end

-- 初始化地图区域
function MapRegion.init()
    -- 获取屏幕尺寸
    local screenWidth, screenHeight = getScreenSize()
    
    -- 计算需要扫描的区域数量
    local regionsX = math.ceil(screenWidth / MapRegion.regionSize.width)
    local regionsY = math.ceil(screenHeight / MapRegion.regionSize.height)
    MapRegion.progress.totalRegions = regionsX * regionsY
    
    -- 初始化待扫描区域
    for x = 0, regionsX - 1 do
        for y = 0, regionsY - 1 do
            table.insert(MapRegion.pendingAreas, {
                x = x * MapRegion.regionSize.width,
                y = y * MapRegion.regionSize.height,
                width = MapRegion.regionSize.width,
                height = MapRegion.regionSize.height
            })
        end
    end
    
    log("初始化地图区域完成，共" .. MapRegion.progress.totalRegions .. "个区域")
end

-- 标记区域为已扫描
function MapRegion.markAsScanned(region)
    table.insert(MapRegion.scannedAreas, region)
    MapRegion.progress.scannedRegions = MapRegion.progress.scannedRegions + 1
    log("已扫描区域: " .. MapRegion.progress.scannedRegions .. "/" .. MapRegion.progress.totalRegions)
end

-- 获取下一个待扫描区域
function MapRegion.getNextArea()
    if #MapRegion.pendingAreas > 0 then
        return table.remove(MapRegion.pendingAreas, 1)
    end
    return nil
end

-- 根据NPC位置推测新的扫描区域
function MapRegion.predictNewAreas(npcPositions)
    local newAreas = {}
    
    -- 分析NPC分布规律
    for _, npc in ipairs(npcPositions) do
        -- 检查NPC周围区域
        local surroundingAreas = {
            {x = npc.x - MapRegion.regionSize.width, y = npc.y},
            {x = npc.x + MapRegion.regionSize.width, y = npc.y},
            {x = npc.x, y = npc.y - MapRegion.regionSize.height},
            {x = npc.x, y = npc.y + MapRegion.regionSize.height}
        }
        
        -- 添加推测的区域
        for _, area in ipairs(surroundingAreas) do
            -- 检查区域是否已扫描或已在待扫描队列中
            local isNewArea = true
            for _, scannedArea in ipairs(MapRegion.scannedAreas) do
                if area.x == scannedArea.x and area.y == scannedArea.y then
                    isNewArea = false
                    break
                end
            end
            
            if isNewArea then
                for _, pendingArea in ipairs(MapRegion.pendingAreas) do
                    if area.x == pendingArea.x and area.y == pendingArea.y then
                        isNewArea = false
                        break
                    end
                end
            end
            
            if isNewArea then
                table.insert(newAreas, {
                    x = area.x,
                    y = area.y,
                    width = MapRegion.regionSize.width,
                    height = MapRegion.regionSize.height
                })
            end
        end
    end
    
    return newAreas
end

-- 移动到指定区域中心
function MapRegion.moveToArea(area)
    local targetX = area.x + area.width / 2
    local targetY = area.y + area.height / 2
    
    log("移动到区域中心: " .. targetX .. "," .. targetY)
    moveToPosition(targetX, targetY)
    mSleep(1000) -- 等待移动完成
end

-- 显示扫描进度
function showScanProgress()
    local progress = MapRegion.progress
    local percent = math.floor((progress.scannedRegions / progress.totalRegions) * 100)
    
    local progressText = string.format(
        "扫描进度: %d/%d (%d%%)\n已发现NPC: %d\n待扫描区域: %d",
        progress.scannedRegions,
        progress.totalRegions,
        percent,
        #npcDatabase.npcs,
        #MapRegion.pendingAreas
    )
    
    dialog(progressText, 1)
end

-- 修改scanAndAnalyzeMap函数，添加进度显示
function scanAndAnalyzeMap()
    log("开始扫描和分析地图...")
    
    -- 加载已有的NPC数据库
    loadNPCDatabase()
    
    -- 加载关键词库
    loadKeywordsLibrary()
    
    -- 初始化地图区域
    MapRegion.init()
    
    -- 扫描循环
    while true do
        -- 显示进度
        showScanProgress()
        
        -- 获取下一个待扫描区域
        local currentArea = MapRegion.getNextArea()
        if not currentArea then
            log("所有区域扫描完成")
            break
        end
        
        -- 移动到当前区域
        MapRegion.moveToArea(currentArea)
        
        -- 截取当前区域画面
        local screenPath = captureGameScreen()
        local base64Image = imageToBase64(screenPath)
        if not base64Image then
            log("错误: 图像转Base64失败")
            MapRegion.markAsScanned(currentArea)
            goto continue
        end
        
        -- 发送到ChatGPT分析
        local request = buildNPCDetectionRequest(base64Image)
        local response = sendToChatGPT(request)
        if not response then
            log("错误: 获取ChatGPT响应失败")
            MapRegion.markAsScanned(currentArea)
            goto continue
        end
        
        -- 解析NPC位置
        local npcPositions = parseNPCLocations(response)
        log("在当前区域识别到" .. #npcPositions .. "个NPC")
        
        -- 根据NPC位置推测新的扫描区域
        local newAreas = MapRegion.predictNewAreas(npcPositions)
        for _, area in ipairs(newAreas) do
            table.insert(MapRegion.pendingAreas, area)
        end
        
        -- 逐个与NPC交互并分析
        for _, npc in ipairs(npcPositions) do
            -- 与NPC交互并截取对话
            local dialogPath = interactWithNPC(npc)
            local dialogBase64 = imageToBase64(dialogPath)
            if not dialogBase64 then
                log("错误: 对话图像转Base64失败")
                goto continue_npc
            end
            
            -- 分析对话判断副本功能
            local dialogRequest = buildDialogAnalysisRequest(dialogBase64)
            local dialogResponse = sendToChatGPT(dialogRequest)
            if not dialogResponse then
                log("错误: 获取对话分析响应失败")
                goto continue_npc
            end
            
            local dungeonInfo = parseDungeonInfo(dialogResponse)
            
            -- 更新数据库
            updateNPCInfo(npc, dungeonInfo)
            
            -- 关闭当前对话
            tap(width*0.9, height*0.1) -- 假设关闭按钮在右上角
            mSleep(1000)
            
            ::continue_npc::
        end
        
        -- 标记当前区域为已扫描
        MapRegion.markAsScanned(currentArea)
        
        ::continue::
    end
    
    log("地图扫描和分析完成")
    return true
end

-- UI配置
function createUIConfig()
    -- 确保已初始化屏幕尺寸
    if width == 0 or height == 0 then
        width, height = getScreenSize()
    end
    
    return {
        style = "default",
        width = width,
        height = height,
        config = "npc_scanner_config.dat",
        timer = 30,
        orient = 0,
        pagetype = "single",
        title = "NPC扫描器",
        cancelname = "取消",
        okname = "开始扫描",
        views = {
            {
                type = "Label",
                text = "NPC扫描器设置",
                size = 20,
                align = "center",
                color = "0,168,233"
            },
            {
                type = "Label",
                text = "OpenAI API密钥设置",
                size = 16,
                align = "left",
                color = "0,168,233"
            },
            {
                type = "Edit",
                id = "api_key",
                prompt = "输入OpenAI API密钥",
                text = apiKey,
                kbtype = "ascii"
            },
            {
                type = "Label",
                text = "扫描选项",
                size = 16,
                align = "left",
                color = "0,168,233"
            },
            {
                type = "RadioGroup",
                id = "scan_mode",
                list = "新扫描,使用已保存数据",
                select = "1"
            },
            {
                type = "Label",
                text = "等待时间设置(毫秒)",
                size = 16,
                align = "left",
                color = "0,168,233"
            },
            {
                type = "Edit",
                id = "move_delay",
                prompt = "移动后等待时间",
                text = "1000",
                kbtype = "number"
            },
            {
                type = "Edit",
                id = "dialog_delay",
                prompt = "对话后等待时间",
                text = "1500",
                kbtype = "number"
            }
        }
    }
end

-- 显示配置界面
function showConfig()
    -- 将UI配置转换为JSON字符串
    local configJson = json.encode(createUIConfig())
    log("UI配置已生成")
    
    -- 显示UI配置界面
    local ret, values = showUI(configJson)
    
    log("showUI返回值: " .. tostring(ret))
    if ret == 1 then
        -- 更新API密钥
        if values.api_key and values.api_key ~= "" then
            apiKey = values.api_key
            log("已设置API密钥")
        end
        
        -- 设置等待时间
        if values.move_delay then
            moveDelay = tonumber(values.move_delay) or 1000
            log("设置移动等待时间为: " .. moveDelay .. " 毫秒")
        end
        
        if values.dialog_delay then
            dialogDelay = tonumber(values.dialog_delay) or 1500
            log("设置对话等待时间为: " .. dialogDelay .. " 毫秒")
        end
        
        -- 扫描模式
        if values.scan_mode == "0" then
            log("选择新扫描模式")
            return true
        else
            log("选择使用已保存数据")
            loadNPCDatabase()
            return false
        end
    else
        return false
    end
end

-- 导出NPC数据
function exportNPCData()
    local data = {
        npcs = npcDatabase.npcs,
        dungeonKeywords = dungeonKeywords
    }
    
    local filePath = userPath().."/npc_export.json"
    local file = io.open(filePath, "w")
    if file then
        file:write(json.encode(data))
        file:close()
        log("NPC数据已导出到: " .. filePath)
        return true
    else
        log("错误: 无法导出NPC数据到: " .. filePath)
        return false
    end
end

-- 主函数
function main()
    -- 显示脚本信息
    log("NPC扫描器 v" .. version .. " 启动")
    
    -- 初始化屏幕尺寸
    init("0", 0) -- 横屏模式
    width, height = getScreenSize()
    log("屏幕分辨率: " .. width .. "x" .. height)
    
    -- 显示配置界面并决定是否扫描
    local needScan = showConfig()
    
    if needScan then
        -- 执行扫描
        local success = scanAndAnalyzeMap()
        if success then
            -- 导出数据
            exportNPCData()
            dialog("扫描完成并导出数据", 0)
        else
            dialog("扫描失败，请检查日志", 0)
        end
    else
        -- 查看已有数据
        if #npcDatabase.npcs > 0 then
            -- 导出数据
            exportNPCData()
            dialog("已加载" .. #npcDatabase.npcs .. "个NPC数据并导出", 0)
        else
            dialog("未找到NPC数据，请执行新扫描", 0)
        end
    end
end

-- 启动脚本
main() 